/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-07-11     acer       the first version
 */
#include "tcp2rtu.h"
#include <rtthread.h>
#include <modbus.h>
#include <modbus-private.h>
#include <stdio.h>
#include <string.h>
#include <dfs_posix.h>
#include <sys/time.h>
#include <dfs_select.h>
#include <sal_socket.h>
#include <sys/socket.h> /* 使用BSD socket，需要包含socket.h头文件 */
#include "netdb.h"
#include <arpa/inet.h>         /* 包含 ip_addr_t 等地址相关的头文件 */
#include <netdev.h>            /* 包含全部的 netdev 相关操作接口函数 */
#include "setting.h"
#include <rtdevice.h>
#include <board.h>

#define RS485_RE GET_PIN(C, 0)

extern device_param_t device_setting;

static void tcpc2rtu_thread(void *param)
{
    struct hostent *host = RT_NULL;
    char ip[16];

    modbus_t *ctx = RT_NULL;
    modbus_mapping_t *mb_mapping = NULL;
    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
    int rc;
    rt_uint16_t tab_reg[64] = {0};
    rt_uint8_t tab_bit[512] = {0};

    //RTU
    modbus_t *ctx_rtu = RT_NULL;
    char parity = 'N';
    if(device_setting->down_dev == 0)
    {
        switch(device_setting->rs232_params.parity)
        {
            case PARITY_NONE:
                parity = 'N';
                break;
            case PARITY_ODD:
                parity = 'O';
                break;
            case PARITY_EVEN:
                parity = 'E';
                break;
            default:
                break;
        }
        ctx_rtu = modbus_new_rtu("/dev/uart1", device_setting->rs232_params.baud, parity, device_setting->rs232_params.data_bit, device_setting->rs232_params.stop_bit + 1);
    }
    else if(device_setting->down_dev == 1)
    {
        switch(device_setting->rs485_params.parity)
        {
            case PARITY_NONE:
                parity = 'N';
                break;
            case PARITY_ODD:
                parity = 'O';
                break;
            case PARITY_EVEN:
                parity = 'E';
                break;
            default:
                break;
        }
        ctx_rtu = modbus_new_rtu("/dev/uart3", device_setting->rs485_params.baud, parity, device_setting->rs485_params.data_bit, device_setting->rs485_params.stop_bit + 1);
    }
    modbus_rtu_set_serial_mode(ctx_rtu, MODBUS_RTU_RS485);
    modbus_rtu_set_rts(ctx_rtu, RS485_RE, MODBUS_RTU_RTS_UP);
    modbus_set_slave(ctx_rtu, 3);
    modbus_connect(ctx_rtu);
    modbus_set_response_timeout(ctx_rtu, 1, 0);

    //判断网卡状态
    while(netdev_get_first_by_flags(NETDEV_FLAG_LINK_UP) == NULL)
    {
        rt_kprintf("Netdev get first by linkup failed!");
        rt_thread_mdelay(1000);
    }
    while(host == NULL)
    {
        /* 通过函数入口参数url获得host地址（如果是域名，会做域名解析） */
        host = gethostbyname(device_setting->server_params.host);
        if (host == RT_NULL)
        {
         rt_thread_mdelay(1000);
         //LOG_E("Get host by name failed!");
        }
    }
    struct in_addr sin_addr = *((struct in_addr *)host->h_addr);
    rt_uint8_t ip_addr[4] = {0, 0, 0, 0};
    rt_memcpy(ip_addr, &sin_addr.s_addr, 4);
    rt_sprintf(ip, "%d.%d.%d.%d", ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
    ctx = modbus_new_tcp(ip, device_setting->server_params.port, AF_INET);
    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, MODBUS_MAX_READ_BITS,
         MODBUS_MAX_READ_REGISTERS, MODBUS_MAX_READ_REGISTERS);
    RT_ASSERT(mb_mapping != RT_NULL);
 _mbtcpc_start:
    if(modbus_connect(ctx) < 0)
        goto _mbtcpc_restart;

    while (1)
    {
         rc = modbus_receive(ctx, query);
         if (rc > 0)
         {
             //读取rtu
             unsigned int offset = ctx->backend->header_length;
             rt_uint16_t address = (query[offset + 1] << 8) + query[offset + 2];
             int nb = (query[offset + 3] << 8) + query[offset + 4];
             //地址
             modbus_set_slave(ctx_rtu, query[offset - 1]);
             modbus_set_slave(ctx, query[offset - 1]);
             //功能码
             int regs = 0;
             uint8_t dest8[512];
             uint16_t dest16[125];
             switch (query[offset])
             {
                case MODBUS_FC_READ_COILS:
                {
                    regs = modbus_read_bits(ctx_rtu, address, nb, dest8);
                    if(regs > -1)
                    {
                        rt_memcpy(mb_mapping->tab_bits + address, dest8, nb);
                    }
                    break;
                }
                case MODBUS_FC_READ_DISCRETE_INPUTS:
                {
                    regs = modbus_read_input_bits(ctx_rtu, address, nb, dest8);
                    if(regs > -1)
                    {
                        rt_memcpy(mb_mapping->tab_input_bits + address, dest8, nb);
                    }
                    break;
                }
                case MODBUS_FC_READ_HOLDING_REGISTERS:
                {
                    regs = modbus_read_registers(ctx_rtu, address, nb, dest16);
                    if(regs > -1)
                    {
                        rt_memcpy(mb_mapping->tab_registers + address, dest16, nb * 2);
                    }
                    break;
                }
                case MODBUS_FC_READ_INPUT_REGISTERS:
                {
                    regs = modbus_read_input_registers(ctx_rtu, address, nb, dest16);
                    if(regs > -1)
                    {
                        rt_memcpy(mb_mapping->tab_input_registers + address, dest16, nb * 2);
                    }
                    break;
                }
                case MODBUS_FC_WRITE_SINGLE_COIL:
                {
                    int status = (query[offset + 3] << 8) + query[offset + 4];
                    regs = modbus_write_bit(ctx_rtu, address, status);
                    break;
                }
                case MODBUS_FC_WRITE_SINGLE_REGISTER:
                {
                    rt_uint16_t value = (query[offset + 3] << 8) + query[offset + 4];
                    regs = modbus_write_register(ctx_rtu, address, value);
                    break;
                }
                case MODBUS_FC_READ_EXCEPTION_STATUS:
                {
                    regs = -1;
                    break;
                }
                case MODBUS_FC_WRITE_MULTIPLE_COILS:
                {
                    int pos = 0, i, temp, bit;
                    for (i = offset + 6; i < offset + 6 + query[offset + 5]; i++)
                    {
                        /* Shift reg hi_byte to temp */
                        temp = query[i];

                        for (bit = 0x01; (bit & 0xff) && (pos < nb);)
                        {
                            tab_bit[pos++] = (temp & bit) ? TRUE : FALSE;
                            bit = bit << 1;
                        }
                    }
                    regs = modbus_write_bits(ctx_rtu, address, nb, tab_bit);
                    break;
                }
                case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
                {
                    rt_memcpy(tab_reg, query + offset + 6, nb * 2);
                    regs = modbus_write_registers(ctx_rtu, address, nb, tab_reg);
                    break;
                }
                case MODBUS_FC_REPORT_SLAVE_ID:
                {
                    //regs = modbus_report_slave_id(ctx_rtu, MODBUS_MAX_PDU_LENGTH, dest);
                    break;
                }
                case MODBUS_FC_MASK_WRITE_REGISTER:
                {
                    //regs = modbus_mask_write_register(ctx, addr, and_mask, or_mask)(ctx_rtu, address, nb, mb_mapping->tab_input_bits);
                    break;
                }
                case MODBUS_FC_WRITE_AND_READ_REGISTERS:
                {
                    rt_uint16_t write_address = (query[offset + 5] << 8) + query[offset + 6];
                    rt_uint16_t write_nb = (query[offset + 7] << 8) + query[offset + 8];
                    rt_memcpy(tab_reg, query + offset + 9 + 1, query[offset + 9]);
                    regs = modbus_write_and_read_registers(ctx_rtu, write_address, write_nb, tab_reg, address, nb, mb_mapping->tab_registers);
                    break;
                }
                default:
                    regs = -1;
                    break;
            }
            if(regs < 0)
            {
                //返回异常
                if(errno - MODBUS_ENOBASE > 0 && errno - MODBUS_ENOBASE < MODBUS_EXCEPTION_MAX)
                {
                    rc = modbus_reply_exception(ctx, query, errno - MODBUS_ENOBASE);
                }
                else
                {
                    rc = modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
                }
            }
            else
            {
                //返回结果tcp
                rc = modbus_reply(ctx, query, rc, mb_mapping);
            }

            if(rc < 0)
            {
                goto _mbtcpc_restart;
            }
         }
         else
         {
             goto _mbtcpc_restart;
         }
    }

 _mbtcpc_restart:
     //7-关闭modbus端口
     modbus_close(ctx);
     rt_thread_mdelay(2000);
     goto _mbtcpc_start;

     //8-释放modbus资源
     modbus_free(ctx);
 }

#define MAX_CLIENT_NUM  3
#define CLIENT_TIMEOUT  10      //单位 s

typedef struct
{
    int fd;
    rt_tick_t tick_timeout;
}client_session_t;

static void tcps2rtu_thread(void *param)
{
    int server_fd = -1;
    modbus_t *ctx = NULL;
    modbus_mapping_t *mb_mapping = NULL;
    client_session_t client_session[MAX_CLIENT_NUM];
    //RTU
    modbus_t *ctx_rtu = RT_NULL;
    char parity = 'N';
    if(device_setting->down_dev == 0)
    {
        switch(device_setting->rs232_params.parity)
        {
            case PARITY_NONE:
                parity = 'N';
                break;
            case PARITY_ODD:
                parity = 'O';
                break;
            case PARITY_EVEN:
                parity = 'E';
                break;
            default:
                break;
        }
        ctx_rtu = modbus_new_rtu("/dev/uart1", device_setting->rs232_params.baud, parity, device_setting->rs232_params.data_bit, device_setting->rs232_params.stop_bit + 1);
    }
    else if(device_setting->down_dev == 1)
    {
        switch(device_setting->rs485_params.parity)
        {
            case PARITY_NONE:
                parity = 'N';
                break;
            case PARITY_ODD:
                parity = 'O';
                break;
            case PARITY_EVEN:
                parity = 'E';
                break;
            default:
                break;
        }
        ctx_rtu = modbus_new_rtu("/dev/uart3", device_setting->rs485_params.baud, parity, device_setting->rs485_params.data_bit, device_setting->rs485_params.stop_bit + 1);
    }
    modbus_rtu_set_serial_mode(ctx_rtu, MODBUS_RTU_RS485);
    modbus_rtu_set_rts(ctx_rtu, RS485_RE, MODBUS_RTU_RTS_UP);
    modbus_set_slave(ctx_rtu, 3);
    modbus_connect(ctx_rtu);
    modbus_set_response_timeout(ctx_rtu, 0, 1000000);

    //判断网卡状态
    while(netdev_get_first_by_flags(NETDEV_FLAG_LINK_UP) == NULL)
    {
        rt_kprintf("Netdev get first by linkup failed!");
        rt_thread_mdelay(1000);
    }

    for (int i = 0; i < MAX_CLIENT_NUM; i++)
    {
        client_session[i].fd = -1;
        client_session[i].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
    }

    int max_fd = -1;
    fd_set readset;
    int rc;
    rt_uint16_t tab_reg[64] = {0};
    rt_uint8_t tab_bit[512] = {0};

    struct timeval select_timeout;
    select_timeout.tv_sec = 1;
    select_timeout.tv_usec = 0;

    ctx = modbus_new_tcp(RT_NULL, device_setting->server_params.port, AF_INET);
    RT_ASSERT(ctx != RT_NULL);
    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, MODBUS_MAX_READ_BITS,
                                    MODBUS_MAX_READ_REGISTERS, MODBUS_MAX_READ_REGISTERS);
    RT_ASSERT(mb_mapping != RT_NULL);

_mbtcps_start:
    server_fd = modbus_tcp_listen(ctx, 1);
    if (server_fd < 0)
        goto _mbtcps_restart;

    while (1)
    {
        max_fd = -1;
        FD_ZERO(&readset);
        FD_SET(server_fd, &readset);

        if(max_fd < server_fd)
            max_fd = server_fd;

        for (int i = 0; i < MAX_CLIENT_NUM; i++)
        {
            if(client_session[i].fd >= 0)
            {
                FD_SET(client_session[i].fd, &readset);
                if(max_fd < client_session[i].fd)
                    max_fd = client_session[i].fd;
            }
        }

        rc = select(max_fd + 1, &readset, RT_NULL, RT_NULL, &select_timeout);
        if(rc < 0)
        {
            goto _mbtcps_restart;
        }
        else if(rc > 0)
        {
            if(FD_ISSET(server_fd, &readset))
            {
                int client_sock_fd = modbus_tcp_accept(ctx, &server_fd);
                if(client_sock_fd >= 0)
                {
                    int index = -1;
                    for (int i = 0; i < MAX_CLIENT_NUM; i++)
                    {
                        if(client_session[i].fd < 0)
                        {
                            index = i;
                            break;
                        }
                    }
                    if(index >= 0)
                    {
                        client_session[index].fd = client_sock_fd;
                        client_session[index].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
                    }
                    else
                    {
                        closesocket(client_sock_fd);
                    }
                }
            }

            for (int i = 0; i < MAX_CLIENT_NUM; i++)
            {
                if(client_session[i].fd >= 0)
                {
                    if(FD_ISSET(client_session[i].fd, &readset))
                    {
                        uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
                        modbus_set_socket(ctx, client_session[i].fd);

                        rc = modbus_receive(ctx, query);
                        if (rc > 0)
                        {
                            //读取rtu
                             unsigned int offset = ctx->backend->header_length;
                             rt_uint16_t address = (query[offset + 1] << 8) + query[offset + 2];
                             int nb = (query[offset + 3] << 8) + query[offset + 4];
                             //地址
                             modbus_set_slave(ctx_rtu, query[offset - 1]);
                             modbus_set_slave(ctx, query[offset - 1]);
                             //功能码
                             int regs = 0;
                             switch (query[offset])
                             {
                                case MODBUS_FC_READ_COILS:
                                {
                                    regs = modbus_read_bits(ctx_rtu, address, nb, mb_mapping->tab_bits);
                                    break;
                                }
                                case MODBUS_FC_READ_DISCRETE_INPUTS:
                                {
                                    regs = modbus_read_input_bits(ctx_rtu, address, nb, mb_mapping->tab_input_bits);
                                    break;
                                }
                                case MODBUS_FC_READ_HOLDING_REGISTERS:
                                {
                                    regs = modbus_read_registers(ctx_rtu, address, nb, mb_mapping->tab_registers);
                                    break;
                                }
                                case MODBUS_FC_READ_INPUT_REGISTERS:
                                {
                                    regs = modbus_read_input_registers(ctx_rtu, address, nb, mb_mapping->tab_input_registers);
                                    break;
                                }
                                case MODBUS_FC_WRITE_SINGLE_COIL:
                                {
                                    int status = (query[offset + 3] << 8) + query[offset + 4];
                                    regs = modbus_write_bit(ctx_rtu, address, status);
                                    break;
                                }
                                case MODBUS_FC_WRITE_SINGLE_REGISTER:
                                {
                                    rt_uint16_t value = (query[offset + 3] << 8) + query[offset + 4];
                                    regs = modbus_write_register(ctx_rtu, address, value);
                                    break;
                                }
                                case MODBUS_FC_READ_EXCEPTION_STATUS:
                                {
                                    regs = -1;
                                    break;
                                }
                                case MODBUS_FC_WRITE_MULTIPLE_COILS:
                                {
                                    int pos = 0, i, temp, bit;
                                    for (i = offset + 6; i < offset + 6 + query[offset + 5]; i++)
                                    {
                                        /* Shift reg hi_byte to temp */
                                        temp = query[i];

                                        for (bit = 0x01; (bit & 0xff) && (pos < nb);)
                                        {
                                            tab_bit[pos++] = (temp & bit) ? TRUE : FALSE;
                                            bit = bit << 1;
                                        }
                                    }
                                    regs = modbus_write_bits(ctx_rtu, address, nb, tab_bit);
                                    break;
                                }
                                case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
                                {
                                    rt_memcpy(tab_reg, query + offset + 6, nb * 2);
                                    regs = modbus_write_registers(ctx_rtu, address, nb, tab_reg);
                                    break;
                                }
                                case MODBUS_FC_REPORT_SLAVE_ID:
                                {
                                    //regs = modbus_report_slave_id(ctx_rtu, MODBUS_MAX_PDU_LENGTH, dest);
                                    break;
                                }
                                case MODBUS_FC_MASK_WRITE_REGISTER:
                                {
                                    //regs = modbus_mask_write_register(ctx, addr, and_mask, or_mask)(ctx_rtu, address, nb, mb_mapping->tab_input_bits);
                                    break;
                                }
                                case MODBUS_FC_WRITE_AND_READ_REGISTERS:
                                {
                                    rt_uint16_t write_address = (query[offset + 5] << 8) + query[offset + 6];
                                    rt_uint16_t write_nb = (query[offset + 7] << 8) + query[offset + 8];
                                    rt_memcpy(tab_reg, query + offset + 9 + 1, query[offset + 9]);
                                    regs = modbus_write_and_read_registers(ctx_rtu, write_address, write_nb, tab_reg, address, nb, mb_mapping->tab_registers);
                                    break;
                                }
                                default:
                                    regs = -1;
                                    break;
                            }
                            if(regs < 0)
                            {
                                //返回异常
                                if(errno - MODBUS_ENOBASE > 0 && errno - MODBUS_ENOBASE < MODBUS_EXCEPTION_MAX)
                                {
                                    rc = modbus_reply_exception(ctx, query, errno - MODBUS_ENOBASE);
                                }
                                else
                                {
                                    rc = modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
                                }
                            }
                            else
                            {
                                //返回结果tcp
                                rc = modbus_reply(ctx, query, rc, mb_mapping);
                            }
                            if(rc < 0)
                            {
                                closesocket(client_session[i].fd);
                                client_session[i].fd = -1;
                            }
                            else
                            {
                                client_session[i].tick_timeout = rt_tick_get() + rt_tick_from_millisecond(CLIENT_TIMEOUT * 1000);
                            }
                        }
                        else
                        {
                            closesocket(client_session[i].fd);
                            client_session[i].fd = -1;
                        }
                    }
                }
            }
        }

        // 客户端超时未收到数据断开
        for(int i =0;i<MAX_CLIENT_NUM;i++)
        {
            if(client_session[i].fd >= 0)
            {
                //超时
                if((rt_tick_get() - client_session[i].tick_timeout) < (RT_TICK_MAX / 2))
                {
                    closesocket(client_session[i].fd);
                    client_session[i].fd = -1;
                }
            }
        }
    }

_mbtcps_restart:
    if(server_fd >= 0)
    {
        closesocket(server_fd);
        server_fd = -1;
    }

    for(int i =0;i<MAX_CLIENT_NUM;i++)
    {
        if(client_session[i].fd >= 0)
        {
            closesocket(client_session[i].fd);
            client_session[i].fd = -1;
        }
    }

    rt_thread_mdelay(5000);
    goto _mbtcps_start;

    modbus_free(ctx);
}

int tcp2rtu_init(void)
{
    rt_thread_t tid;
    if(GET_BIT_OF(device_setting->fun_enable, 0))
    {
        tid = rt_thread_create("tcps2rtu",
                tcps2rtu_thread, RT_NULL,
                2048,
                12, 10);
    }
    else
    {
        tid = rt_thread_create("tcpc2rtu",
                tcpc2rtu_thread, RT_NULL,
                2048,
                12, 10);
    }

    if (tid != RT_NULL)
        rt_thread_startup(tid);
    return RT_EOK;
}
